package com.leizi.igym.service.impl;

import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.leizi.igym.BaseContext.UserThreadLocal;
import com.leizi.igym.constant.JwtClaimsConstant;
import com.leizi.igym.constant.Salt;
import com.leizi.igym.constant.UserConstant;
import com.leizi.igym.domain.User;
import com.leizi.igym.dto.DoughnutStatisticsDTO;
import com.leizi.igym.dto.LineStatisticsDTO;
import com.leizi.igym.dto.UserPageQueryDTO;
import com.leizi.igym.enums.DoughnutStatisticsTypeEnum;
import com.leizi.igym.enums.LineStatisticsDateEnum;
import com.leizi.igym.enums.ReturnCodeEnum;
import com.leizi.igym.exception.BusinessException;
import com.leizi.igym.pojo.ContextUser;
import com.leizi.igym.properties.JWTProperties;
import com.leizi.igym.properties.WxProperties;
import com.leizi.igym.service.UserService;
import com.leizi.igym.mapper.UserMapper;
import com.leizi.igym.utils.*;
import com.leizi.igym.vo.UserLoginVO;
import com.leizi.igym.vo.UserVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author PC
 * @description 针对表【t_user(健身房用户表)】的数据库操作Service实现
 * @createDate 2024-04-04 09:07:42
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Resource
    private JWTProperties jwtProperties;

    @Resource
    private WxProperties wxProperties;

    /**
     * 获取登录条件构造器
     *
     * @param: [account, password]
     * @return: com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<com.leizi.igym.domain.User>
     **/
    @Override
    public QueryWrapper<User> getLoginQueryWrapper(String account, String password) {
        if (StringUtils.isAnyBlank(account, password)) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }
        QueryWrapper<User> qw = new QueryWrapper<>();
        qw.eq("username", account);
        // System.out.println(PwdEncryptUtils.encryptPwd(password));
        qw.eq("user_password", PwdEncryptUtils.encryptPwd(password));
        return qw;
    }

    /**
     * 用户端登录
     *
     * @param: [username, password]
     * @return: com.leizi.igym.vo.UserLoginVO
     **/
    @Override
    public UserLoginVO userLoginInUser(String username, String password) {
        // 校验用户名和密码是否合法
        ThrowUtils.throwIf(StringUtils.isAnyBlank(username, password), ReturnCodeEnum.PARAMS_ERROR);
        //  todo 校验用户名和密码格式合法

        // 创建条件构造器
        QueryWrapper<User> qw = new QueryWrapper<>();
        qw.eq("username", username);
        qw.eq("user_password", PwdEncryptUtils.encryptPwd(Salt.USER_PWD_SALT, password));
        qw.eq("status", UserConstant.STATUS_NORMAL);
        User dbUser = this.getBaseMapper().selectOne(qw);

        // 判断数据库是否存在该用户
        ThrowUtils.throwIf(Objects.isNull(dbUser), ReturnCodeEnum.PARAMS_ERROR, "用户不存在或密码错误");

        // 判断用户是否可用
        ThrowUtils.throwIf(dbUser.getStatus() == UserConstant.STATUS_LOCKED,
                ReturnCodeEnum.PARAMS_ERROR, "用户账号异常，请联系管理员!");

        // 创建token
        HashMap<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.USER_CLAIMS_ID, dbUser.getId());
        String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(),
                jwtProperties.getUserTtl(),
                claims);

        // 封装VO对象返回
        UserLoginVO userLoginVO = toUserLoginVO(dbUser);
        userLoginVO.setToken(token);
        return userLoginVO;
    }

    /**
     * 普通User对象转化为UserLoginVO对象
     *
     * @param: [user]
     * @return: com.leizi.igym.vo.UserLoginVO
     **/
    @Override
    public UserLoginVO toUserLoginVO(User user) {
        ThrowUtils.throwIf(Objects.isNull(user), ReturnCodeEnum.SYSTEM_ERROR);
        UserLoginVO userLoginVO = new UserLoginVO();
        BeanUtils.copyProperties(user, userLoginVO);
        return userLoginVO;
    }

    /**
     * 用户端注册用户
     *
     * @param: [username, password, checkPassword]
     * @return: java.lang.Long
     **/
    @Override
    public Long registerUser(String username, String password, String checkPassword) {
        // 1. 校验
        if (StringUtils.isAnyBlank(username, password, checkPassword)) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR, "参数为空");
        }
        if (username.length() < 4) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR, "用户账号过短");
        }
        if (password.length() < 8 || checkPassword.length() < 8) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR, "用户密码过短");
        }
        // 密码和校验密码相同
        if (!password.equals(checkPassword)) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR, "两次输入的密码不一致");
        }
        synchronized (username.intern()) {
            // 账户不能重复
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("username", username);
            long count = this.baseMapper.selectCount(queryWrapper);
            if (count > 0) {
                throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR, "账号重复");
            }
            // 2. 加密
            String encryptPassword = PwdEncryptUtils.encryptPwd(Salt.USER_PWD_SALT, password);
            // 3. 插入数据
            String nickname = UserConstant.USER_NICKNAME_PREFIX + RandomStringUtils.randomAlphanumeric(12);
            User user = new User();
            user.setUsername(username);
            user.setUserPassword(encryptPassword);
            user.setNickname(nickname);
            user.setAvatar(UserConstant.USER_DEFAULT_AVATAR_URL);
            boolean saveResult = this.save(user);
            if (!saveResult) {
                throw new BusinessException(ReturnCodeEnum.SYSTEM_ERROR, "注册失败，数据库错误");
            }
            return user.getId();
        }
    }

    /**
     * 修改用户状态
     *
     * @param: [id, status]
     * @return: boolean
     **/
    @Override
    public boolean modifyStatus(Long id, Integer status) {
        // 校验用户id是否合法
        ThrowUtils.throwIf(Objects.isNull(id) || id < 0, ReturnCodeEnum.PARAMS_ERROR, "用户id有误！");

        // 检验状态值是否合法
        ThrowUtils.throwIf(Objects.isNull(status) ||
                        !UserConstant.USER_STATUS_LIST.contains(status)
                , ReturnCodeEnum.PARAMS_ERROR, "状态参数有误！");


        // 查询记录
        User dbUser = getById(id);
        ThrowUtils.throwIf(Objects.isNull(dbUser), ReturnCodeEnum.NOT_FOUND_ERROR);
        ThrowUtils.throwIf(dbUser.getStatus() == status, ReturnCodeEnum.STATUS_ERROR);

        // 修改状态
        dbUser.setStatus(status);
        Integer result = getBaseMapper().updateById(dbUser);
        return null != result && result >= 1;
    }

    /**
     * 更新用户密码
     *
     * @param: [id, originPassword, newPassword, checkPassword]
     * @return: boolean
     **/
    @Override
    public boolean updatePassword(Long id, String originPassword, String newPassword, String checkPassword) {
        // 检验id是否合法
        ThrowUtils.throwIf(Objects.isNull(id) || id <= 0, ReturnCodeEnum.PARAMS_ERROR);

        // 检验密码相关参数是否合法
        ThrowUtils.throwIf(StringUtils.isAnyBlank(originPassword, newPassword, checkPassword),
                ReturnCodeEnum.PARAMS_ERROR);
        ThrowUtils.throwIf(!newPassword.equals(checkPassword), ReturnCodeEnum.PARAMS_ERROR);

        // 从数据库获取用户信息
        QueryWrapper<User> qw = new QueryWrapper<>();
        qw.eq("id", id);
        qw.eq("user_password", PwdEncryptUtils.encryptPwd(Salt.USER_PWD_SALT, originPassword));
        User dbUser = this.getBaseMapper().selectOne(qw);
        ThrowUtils.throwIf(Objects.isNull(dbUser), ReturnCodeEnum.NOT_FOUND_ERROR, "该用户信息不匹配！");

        // 修改密码
        dbUser.setUserPassword(PwdEncryptUtils.encryptPwd(Salt.USER_PWD_SALT, newPassword));
        Integer result = this.getBaseMapper().updateById(dbUser);
        return null != result && result >= 1;
    }

    /**
     * 根据用户id获取用户UserVO对象
     *
     * @param: [id]
     * @return: com.leizi.igym.vo.UserVO
     **/
    @Override
    public UserVO getUserById(Long id) {
        // 检验id是否合法
        ThrowUtils.throwIf(Objects.isNull(id) || id <= 0, ReturnCodeEnum.PARAMS_ERROR);

        // 根据id查询用户
        User dbUser = this.getBaseMapper().selectById(id);
        ThrowUtils.throwIf(Objects.isNull(dbUser), ReturnCodeEnum.NOT_FOUND_ERROR, "该用户未找到！");
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(dbUser, userVO);
        return userVO;
    }

    /**
     * 获取对象条件查询构造器对象
     *
     * @param: [dto]
     * @return: com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<com.leizi.igym.domain.User>
     **/
    @Override
    public QueryWrapper<User> getUserPageQueryWrapper(UserPageQueryDTO dto) {
        ThrowUtils.throwIf(Objects.isNull(dto), ReturnCodeEnum.PARAMS_ERROR);
        String username = dto.getUsername();
        String nickname = dto.getNickname();
        Integer gender = dto.getGender();
        Integer status = dto.getStatus();
        Integer roleId = dto.getRoleId();
        LocalDate beginTime = dto.getBeginTime();
        LocalDate endTime = dto.getEndTime();
        String phone = dto.getPhone();

        QueryWrapper<User> qW = new QueryWrapper<>();
        qW.like(StringUtils.isNotBlank(username), "username", username);
        qW.like(StringUtils.isNotBlank(nickname), "nickname", nickname);
        qW.like(StringUtils.isNotBlank(phone), "phone", phone);

        // gender合法性校验
        ThrowUtils.throwIf(!UserConstant.GENDER_LIST.contains(gender) && gender != null,
                ReturnCodeEnum.PARAMS_ERROR, "性别输入有误！");
        if (UserConstant.GENDER_LIST.contains(gender)) {
            qW.eq("gender", gender);
        }

        // status合法性校验
        ThrowUtils.throwIf(!UserConstant.USER_STATUS_LIST.contains(status) && status != null,
                ReturnCodeEnum.PARAMS_ERROR, "性别输入有误！");
        if (UserConstant.USER_STATUS_LIST.contains(status)) {
            qW.eq("status", status);
        }

        // todo 获取角色列表，校验roleId是否合法

        // 校验日期
        // if (ObjectUtils.allNotNull(beginTime, endTime) && endTime.isAfter(beginTime)) {
        //     ThrowUtils.throwIf(!endTime.isAfter(beginTime), ReturnCodeEnum.PARAMS_ERROR, "传入的日期有误！");
        // }
        // if (Objects.nonNull(beginTime)) {
        //     qW.ge("create_time", LocalDateTime.of(beginTime, LocalTime.MIN));
        // }
        // if (Objects.nonNull(endTime)) {
        //     qW.le("create_time", LocalDateTime.of(endTime, LocalTime.MAX));
        // }
        DateQWUtils.getDateQWForTrainer(qW, beginTime, endTime);
        return qW;
    }

    /**
     * 管理端修改用户
     *
     * @param: [user]
     * @return: boolean
     **/
    @Override
    public boolean editByIdInAdmin(User user) {
        ThrowUtils.throwIf(ObjectUtils.allNull(user, user.getId()), ReturnCodeEnum.PARAMS_ERROR);
        QueryWrapper<User> qw = new QueryWrapper<>();
        qw.eq("id", user.getId());
        long count = count(qw);
        ThrowUtils.throwIf(count < 1, ReturnCodeEnum.NOT_FOUND_ERROR, "修改对象未找到！");
        Integer i = getBaseMapper().updateById(user);
        return null != i && i >= 1;
    }

    /**
     * 用户端修改用户
     *
     * @param user
     * @return: boolean
     **/
    @Override
    public boolean editByIdInUser(User user) {
        ContextUser contextUser = UserThreadLocal.get();
        log.info("当前上下文用户为：{}", contextUser);
        // 校验登录态
        ThrowUtils.throwIf(Objects.isNull(contextUser), ReturnCodeEnum.NOT_LOGIN_ERROR, "登录状态异常！");
        // 检验上面两者关系
        ThrowUtils.throwIf(!Objects.equals(contextUser.getId(), user.getId()), ReturnCodeEnum.PARAMS_ERROR,
                "修改数据与当前用户不匹配！");
        Integer result = this.getBaseMapper().updateById(user);
        return null != result && result >= 1;
    }

    /**
     * 用户端修改个人密码
     *
     * @param originPassword
     * @param newPassword
     * @param checkPassword
     * @return: boolean
     **/
    @Override
    public boolean updatePasswordInUser(String originPassword, String newPassword, String checkPassword) {
        // 密码的非空检验
        ThrowUtils.throwIf(ObjectUtils.allNull(originPassword,
                        newPassword,
                        checkPassword),
                ReturnCodeEnum.PARAMS_ERROR);

        // 判断两次密码是否输入一样
        ThrowUtils.throwIf(newPassword.equals(originPassword), ReturnCodeEnum.PARAMS_ERROR, "新旧密码一致辣！");

        // 判断新密码和检验码是否一致
        ThrowUtils.throwIf(!checkPassword.equals(newPassword), ReturnCodeEnum.PARAMS_ERROR, "两次密码输入不一致！");

        // 获取登录态
        ContextUser contextUser = UserThreadLocal.get();
        ThrowUtils.throwIf(Objects.isNull(contextUser), ReturnCodeEnum.NOT_LOGIN_ERROR);

        // 查询数据库
        QueryWrapper<User> qw = new QueryWrapper<>();
        Long id = contextUser.getId();
        qw.eq(ValidateUtils.validateId(id), "id", id);
        qw.eq("user_password", PwdEncryptUtils.encryptPwd(Salt.USER_PWD_SALT, originPassword));
        Long count = this.getBaseMapper().selectCount(qw);

        // 判断是否存在唯一用户
        ThrowUtils.throwIf(count != 1, ReturnCodeEnum.NOT_FOUND_ERROR);

        // 修改密码
        UpdateWrapper<User> uw = new UpdateWrapper<>();
        uw.eq("id", id).set("user_password", PwdEncryptUtils.encryptPwd(Salt.USER_PWD_SALT, newPassword));
        Integer result = getBaseMapper().update(uw);
        return null != result && result >= 1;
    }

    @Override
    public UserLoginVO wxLogin(String code) throws IOException {
        if (StringUtils.isBlank(code)) {
            throw new BusinessException(ReturnCodeEnum.WXLOGIN_ERROR);
        }
        String openid = getOpenid(code);
        if (StringUtils.isBlank(openid)) {
            throw new BusinessException(ReturnCodeEnum.WXLOGIN_ERROR);
        }
        QueryWrapper<User> qw = new QueryWrapper<>();
        qw.eq("openid", openid);
        UserLoginVO userLoginVO = new UserLoginVO();
        User dbUser = this.getBaseMapper().selectOne(qw);

        if (Objects.nonNull(dbUser)) {
            // 创建token
            HashMap<String, Object> claims = new HashMap<>();
            claims.put(JwtClaimsConstant.USER_CLAIMS_ID, dbUser.getId());
            String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(),
                    jwtProperties.getUserTtl(),
                    claims);
            BeanUtils.copyProperties(dbUser, userLoginVO);
            userLoginVO.setToken(token);
            return userLoginVO;
        }
        User user = new User();
        user.setOpenid(openid);
        user.setNickname(UserConstant.USER_NICKNAME_PREFIX + RandomStringUtils.randomAlphanumeric(12));
        user.setAvatar(UserConstant.USER_DEFAULT_AVATAR_URL);
        int insert = this.getBaseMapper().insert(user);
        if (insert < 1) {
            throw new BusinessException(ReturnCodeEnum.SAVE_ERROR, "微信登录——新增用户异常！");
        }
        // 创建token
        HashMap<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.USER_CLAIMS_ID, user.getId());
        String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(),
                jwtProperties.getUserTtl(),
                claims);
        BeanUtils.copyProperties(user, userLoginVO);
        return userLoginVO;
    }

    @Override
    public LineStatisticsDTO getLineStatisticsByDate(Integer date) {
        if (Objects.isNull(date) || Objects.isNull(LineStatisticsDateEnum.getDateValue(date))) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }
        Map map = new HashMap<>();
        List<String> dateList = new ArrayList<>();
        List<Integer> newCountList = new ArrayList<>();
        List<Integer> allCountList = new ArrayList<>();
        LocalDate now = LocalDate.now();

        for (int i = date - 1; i >= 0; i--) {
            LocalDate today = now.minusDays(i);
            dateList.add(today.toString());
            LocalDateTime endTime = LocalDateTime.of(today, LocalTime.MAX);
            map.put("endTime", endTime);
            allCountList.add(this.getBaseMapper().selectAllCount(map));
            LocalDateTime beginTime = LocalDateTime.of(today, LocalTime.MIN);
            map.put("beginTime", beginTime);
            newCountList.add(this.getBaseMapper().selectNewCount(map));
        }
        return LineStatisticsDTO
                .builder()
                .dateList(dateList)
                .newCountList(newCountList)
                .allCountList(allCountList)
                .build();
    }


    private String getOpenid(String code) throws IOException {
        CloseableHttpClient httpClient = null;
        HttpGet get = null;
        try {
            httpClient = HttpClients.createDefault();
            get = new HttpGet();
            get.setURI(new URI("https://api.weixin.qq.com/sns/jscode2session?appid=" + wxProperties.getAppId() +
                    "&secret=" + wxProperties.getSecret() +
                    "&js_code=" + code +
                    "&grant_type=authorization_code"));
            CloseableHttpResponse response = httpClient.execute(get);
            JSONObject jsonObject = JSON.parseObject(response.getEntity().getContent());
            String openid = (String) jsonObject.get("openid");
            return openid;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            httpClient.close();
        }
        return "";
    }
}




